# -*- coding: utf-8 -*-
'''
Created on Mar 8, 2011

@author: Peter
'''
from numpy import *


from numpy import linalg as la

def loadExData0():
    return [[0, 0, 0, 2, 2],
            [0, 0, 0, 3, 3],
            [0, 0, 0, 1, 1],
            [1, 1, 1, 0, 0],
            [2, 2, 2, 0, 0],
            [5, 5, 5, 0, 0],
            [1, 1, 1, 0, 0]]


def loadExData():
    return [[4, 4, 0, 2, 2],
            [4, 0, 0, 3, 3],
            [4, 0, 0, 1, 1],
            [1, 1, 1, 2, 0],
            [2, 2, 2, 0, 0],
            [1, 1, 1, 0, 0],
            [5, 5, 5, 0, 0]]


def loadExData2():
    return [[0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 5],
            [0, 0, 0, 3, 0, 4, 0, 0, 0, 0, 3],
            [0, 0, 0, 0, 4, 0, 0, 1, 0, 4, 0],
            [3, 3, 4, 0, 0, 0, 0, 2, 2, 0, 0],
            [5, 4, 5, 0, 0, 0, 0, 5, 5, 0, 0],
            [0, 0, 0, 0, 5, 0, 1, 0, 0, 5, 0],
            [4, 3, 4, 0, 0, 0, 0, 5, 5, 0, 1],
            [0, 0, 0, 4, 0, 4, 0, 0, 0, 0, 4],
            [0, 0, 0, 2, 0, 2, 5, 0, 0, 1, 2],
            [0, 0, 0, 0, 5, 0, 0, 0, 0, 4, 0],
            [1, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0]]


def loadExData1():
    return [[2, 0, 0, 4, 4, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5],
            [0, 0, 0, 0, 0, 0, 0, 1, 0, 4, 0],
            [3, 3, 4, 0, 3, 0, 0, 2, 2, 0, 0],
            [5, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 5, 0, 0, 5, 0],
            [4, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5],
            [0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4],
            [0, 0, 0, 0, 0, 0, 5, 0, 0, 5, 0],
            [0, 0, 0, 3, 0, 0, 0, 0, 4, 5, 0],
            [1, 1, 2, 1, 1, 2, 1, 0, 4, 5, 0]]


# 基于欧氏距离的相似度：1/(1+欧氏距离)
def ecludSim(inA, inB):  # A,B两个样本向量
    # inA/inB,是matrix类型
    diff = inA - inB
    diff1 = diff.A ** 2  # 各元素平方, .A是将数据类型转化为array
    return 1.0 / (1.0 + sqrt(diff1.sum()))

def ecludSim2(inA,inB):
    return 1.0/(1.0 + la.norm(inA - inB))

# 基于皮尔森相关系数的相似度：0.5+0.5*corrcoef()
def pearsSim(inA, inB):
    if len(inA) < 3: return 1.0
    # 并将值范围[-1,1]归一化到[0,1]
    corrcoefMat = corrcoef(inA, inB, rowvar=0)  # 2*2的对称矩阵
    # corrcoefMat2 = corrcoef(inA, inB) #结果都是nan
    # 索A引[0][1]或者[1][0]都行
    return 0.5 + 0.5 * corrcoefMat[0][1]


# 基于余弦夹角的相似度
def cosSim(inA, inB):
    num = float(inA.T * inB)
    inALength = sqrt(float(inA.T * inA))
    inBLength = sqrt(float(inB.T * inB))
    denom = inALength * inBLength
    # 将余弦夹角值[-1,1]归一化到[0,1]
    return 0.5 + 0.5 * (num / denom)

def cosSim2(inA,inB):
    num = float(inA.T*inB)
    denom = la.norm(inA)*la.norm(inB)
    return 0.5+0.5*(num/denom)

# 基于物品相似度，计算用户对物品的评分估计值
# 输入：dataMat 用户数据
# user：用户编号（行）
# simMeas：相似度计算函数
# item:物品编号（列），用户待预测的物品
def standEst(dataMat, user, simMeas, item):
    # 数据矩阵列，即为物品数
    n = shape(dataMat)[1]
    simTotal = 0.0;
    ratSimTotal = 0.0

    # 遍历所有物品
    for j in range(n):
        # 用户user对j物品评分
        userRating = dataMat[user, j]
        # （1）若未对物品j评分，即userRating=0，不处理
        if userRating == 0: continue
        # （2）若对物品j评分：
        # 统计对物品item和物品j都评分的用户编号
        overLap = nonzero(logical_and(dataMat[:, item].A > 0, \
                                      dataMat[:, j].A > 0))[0]
        # （2.1）若没有用户同时对物品item和j评分，则两物品间相似度为0
        if len(overLap) == 0:
            similarity = 0
        # （2.2）若有用户同时对物品item和j评分，抽取出来，计算相似度
        else:
            similarity = simMeas(dataMat[overLap, item], dataMat[overLap, j])
        print 'the %d and %d similarity is: %f' % (item, j, similarity)
        # 相似度求和
        simTotal += similarity
        # 预测用户user对物品item评分总和
        ratSimTotal += similarity * userRating
    if simTotal == 0:
        return 0
    # 归一化预测评分
    else:
        return ratSimTotal / simTotal


# 基于SVD的评分估计
# 输入：dataMat 用户数据
# user：用户编号（行）
# simMeas：相似度计算函数
# item:物品编号（列），用户待预测的物品
def svdEst(dataMat, user, simMeas, item):
    # 物品数
    n = shape(dataMat)[1]
    simTotal = 0.0;
    ratSimTotal = 0.0
    # SVD分解
    U, Sigma, VT = linalg.svd(dataMat)
    # 构建对角矩阵，取前3个奇异值
    # 3个额外算出来的，确保总能量>90%
    Sig3 = mat(eye(3) * Sigma[:3])
    # SVD降维，重构低维空间的物品#.I求逆
    xformedItems = dataMat.T * U[:, :3] * Sig3.I
    # 遍历所有物品
    for j in range(n):
        # 用户user对j物品评分
        userRating = dataMat[user, j]
        # 若未对物品j评分，即userRating=0，不处理
        if userRating == 0 or j == item: continue
        # 在低维空间计算物品j与物品item的相似度
        similarity = simMeas(xformedItems[item, :].T, \
                             xformedItems[j, :].T)
        print 'the %d and %d similarity is: %f' % (item, j, similarity)
        # 相似度求和
        simTotal += similarity
        # 预测用户user对物品item评分总和
        ratSimTotal += similarity * userRating
    if simTotal == 0:
        return 0
    # 归一化预测评分
    else:
        return ratSimTotal / simTotal


# 基于物品相似度的推荐
#遍历用户u未评分的物品，对每个未评分的物品，预测评分分数，然后将按照预测分数的高低排序，取分数最高的N个物品作为最终的推荐结果。
# dataMat：  数据
# user：     用户编号
# N：       选择预测评分最高的N个结果
# simMeas：  相似度计算方法
# estMethod：用户对物品的预测估分方法
def recommend(dataMat, user, N=3, simMeas=cosSim, estMethod=standEst):
    # 找没有被用户user评分的物品
    unratedItems2 = nonzero(dataMat[user, :].A == 0)
    unratedItems = nonzero(dataMat[user, :].A == 0)[1]
    # 若都评分则退出，不需要再推荐
    if len(unratedItems) == 0: return 'you rated everything'
    itemScores = []
    # 遍历未评分的物品
    for item in unratedItems:
        # 预测用户user对为评分物品item的估分
        estimatedScore = estMethod(dataMat, user, simMeas, item)
        # 存（物品编号，对应估分值）
        itemScores.append((item, estimatedScore))
    # 选择最高的估分结果:从高到低排序
    return sorted(itemScores, key=lambda jj: jj[1], reverse=True)[:N]


# *****方便图像显示打印
def printMat(inMat, thresh=0.8):
    for i in range(32):
        for k in range(32):
            # 阈值>0.8，输出1.
            if float(inMat[i, k]) > thresh:
                print 1,
            # 阈值<0.8，输出0.
            else:
                print 0,
        print ''


# *****图像压缩
def imgCompress(numSV=3, thresh=0.8):
    myl = []
    # 读.txt文件，转换为矩阵存储到myMat中
    for line in open('0_5.txt').readlines():
        newRow = []
        for i in range(32):
            newRow.append(int(line[i]))
        myl.append(newRow)
    myMat = mat(myl)

    print "****original matrix******"
    # 输出阈值处理的图像
    printMat(myMat, thresh)

    # SVD分解
    U, Sigma, VT = linalg.svd(myMat)
    # 初始化numSV*numSV的零矩阵SigRecon
    SigRecon = mat(zeros((numSV, numSV)))
    # Sigma对角线前numSV个值重构对角矩阵SigRecon
    for k in range(numSV):
        SigRecon[k, k] = Sigma[k]
    # 重构后的图像矩阵
    reconMat = U[:, :numSV] * SigRecon * VT[:numSV, :]
    print "****reconstructed matrix using %d singular values******" % numSV
    # 输出阈值处理后的重构图像
    print printMat(reconMat, thresh)


if __name__ == "__main__":

    # data = loadExData()
    # data = mat(data)
    # pearsSim(data[:,0],data[:,4])


    # # （1）*****测试奇异值分解的结果：
    # data = [[1, 1, 1, 0, 0],
    #         [2, 2, 2, 0, 0],
    #         [1, 1, 1, 0, 0],
    #         [5, 5, 5, 0, 0],
    #         [1, 1, 0, 2, 2],
    #         [0, 0, 0, 3, 3],
    #         [0, 0, 0, 1, 1]]
    # data = mat(data)

    #(2)*****基于物品相似度的推荐
    data=loadExData()
    # data=loadExData2() #用的是loadExData2()
    data=mat(data)
    itemscores = recommend(data, 1) #对用户2推荐
    print 'recommend result:'
    print itemscores

    #  #(3)*****svd 查看能量分布
    #  data=loadExData2()
    #  u,sigma,vT=linalg.svd(mat(data))
    #  print 'sigma=', sigma
    #  energy=sigma**2
    #  print 'all energy=',sum(energy)#总能量
    #  print '90% of all energy=',sum(energy)*0.9 #总能量的90%
    #  #sigma_len=len(sigma) #奇异值个数
    # # for i in range(sigma_len)
    #  print 'energy of the first 2 Singular value=',sum(energy[:2])#前几个奇异值能量求和
    #  print 'energy of the first 3 Singular value=',sum(energy[:3])


    # ##(4)*****基于svd推荐：
    # data=loadExData2()
    # data=mat(data)
    # itemscores=recommend(data,1,estMethod=svdEst) #对用户1推荐,余弦夹角
    # #对用户1推荐，皮尔逊系数
    # #itemscores=svdRec.recommend(data,1,estMethod=svdEst,simMeas=pearsSim)
    # print 'recommend result:'
    # print itemscores

# ##(5)*****图像压缩
# imgCompress(2)